---
id: job
title: 26. 定时任务/后台任务
sidebar_label: 26. 定时任务/后台任务
---

:::important 版本说明

以下内容只限 `Furion 2.0.0 +` 版本使用。

:::

## 26.1 关于定时任务

顾名思义，定时任务就是在特定的时间或符合某种时间规律执行的任务。通常定时任务有四种时间调度方式：

- `缓隔时间` 方式：延迟多少时间后调配任务，这种方式任务只会被调用一次。
- `间隔时间` 方式：每隔一段固定时间调配任务，无间断调用任务。
- `Cron 表达式` 方法：通过 `Cron` 表达式计算下一次执行时间进行调配任务，可以配置特定时间范围内执行，也可以无间断执行。
- `自定义下次执行时间`：可以通过各种逻辑运算返回下一次执行时间

## 26.2 如何实现

`Furion` 框架提供了两种方式实现定时任务：

- `SpareTime` 静态类：`SpareTime` 静态类提供 `SpareTime.Do([options])` 方式调用。
- `ISpareTimeWorker` 依赖方式：通过自定义类实现 `ISpareTimeWorker` 接口并编写一定规则的方法即可。**需要在 `Startup.cs` 中注册 `services.AddTaskScheduler()`**

## 26.3 缓隔方式使用

### 26.3.1 特定时间后执行

这里演示 `3s` 后执行

```cs {5}
Console.WriteLine("当前时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

// timer 是定时器的对象，包含定时器相关信息
// count 表示执行次数，这里只有一次
SpareTime.DoOnce(3000, (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
});
```

### 26.3.2 配置任务信息

```cs {3}
SpareTime.DoOnce(3000, (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}, "jobName", "描述一下这个任务是干什么的");
```

`jobName` 标识任务的唯一标识，通过这个标识可以启动、暂停、销毁任务。

### 26.3.3 手动启动执行

默认情况下，任务初始化后就立即启动，等待符合的时间就执行，有些时候我们仅仅想初始化时间，不希望立即执行，只需要配置 `startNow` 即可：

```cs {3,6}
SpareTime.DoOnce(3000, (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
},"jobName", startNow: false);

// 手动启动执行
SpareTime.Start("jobName");
```

### 26.3.4 模拟后台执行

有些时候，我们只需要开启新线程去执行一个任务，比如发短信，发邮件，无需配置。

```cs {2}
// 此方法无需主线程等待即可返回，可大大提高性能
SpareTime.DoIt(() => {
    // 这里发送短信，发送邮件或记录访问记录
});
```

还可以指定多长时间后触发，建议 `10-1000` 毫秒之间：

```cs {3}
SpareTime.DoIt(() => {
    // 发送短信
}, 100);
```

### 26.3.5 `ISpareTimeWorker` 方式

```cs {1,8}
public class JobWorker : ISpareTimeWorker
{
    /// <summary>
    /// 3s 后执行
    /// </summary>
    /// <param name="timer"></param>
    /// <param name="count"></param>
    [SpareTime(3000, "jobName", DoOnce = true, StartNow = true)]
    public void DoSomething(SpareTimer timer, long count)
    {
        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    }
}
```

**需要在 `Startup.cs` 中注册 `services.AddTaskScheduler()`**

## 26.4 间隔方式使用

### 26.4.1 每隔一段时间执行

```cs {2}
// 每隔 1s 执行
SpareTime.Do(1000, (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
});
```

### 26.4.2 配置任务信息

```cs {1,4}
SpareTime.Do(1000, (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
}, "jobName", "这是一个计时器任务");
```

### 26.4.3 手动启动执行

```cs {1,4,6}
SpareTime.Do(1000, (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
}, "jobName", startNow:false);

SpareTime.Start("jobName");
```

### 26.4.4 `ISpareTimeWorker` 方式

```cs {1,8}
public class JobWorker : ISpareTimeWorker
{
    /// <summary>
    /// 每隔 3s 执行
    /// </summary>
    /// <param name="timer"></param>
    /// <param name="count"></param>
    [SpareTime(3000, "jobName", StartNow = true)]
    public void DoSomething(SpareTimer timer, long count)
    {
        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        Console.WriteLine($"一共执行了：{count} 次");
    }
}
```

**需要在 `Startup.cs` 中注册 `services.AddTaskScheduler()`**

## 26.5 `Cron` 表达式使用

### 26.5.1 什么是 `Cron` 表达式

Cron 表达式是一个字符串，字符串以 `5` 或 `6` 个空格隔开，分为 6 或 7 个域，每一个域代表一个含义，Cron 有如下两种语法格式：

（1） Seconds Minutes Hours DayofMonth Month DayofWeek Year

（2）Seconds Minutes Hours DayofMonth Month DayofWeek

Cron 从左到右（用空格隔开）：`秒` `分` `小时` `月份中的日期` `月份` `星期中的日期` `年份`

| 字段                   | 允许值                                    | 允许的特殊字符              |
| ---------------------- | ----------------------------------------- | --------------------------- |
| 秒（Seconds）          | `0~59` 的整数                             | `, - \* /` 四个字符         |
| 分（Minutes）          | `0~59` 的整数                             | `, - \* /` 四个字符         |
| 小时（Hours）          | `0~23` 的整数                             | `, - \* /` 四个字符         |
| 日期（DayofMonth）     | `1~31` 的整数（但是你需要考虑你月的天数） | `,- \* ? / L W C` 八个字符  |
| 月份（Month）          | `1~12` 的整数或者 `JAN-DEC`               | `, - \* /` 四个字符         |
| 星期（DayofWeek）      | `1~7` 的整数或者 `SUN-SAT （1=SUN）`      | `, - \* ? / L C #` 八个字符 |
| 年(可选，留空)（Year） | `1970~2099`                               | `, - \* /` 四个字符         |

每一个域都使用数字，但还可以出现如下特殊字符，它们的含义是：

（1）`_`：表示匹配该域的任意值。假如在 `Minutes` 域使用 `\_`, 即表示每分钟都会触发事件。

（2）`?`：只能用在 `DayofMonth` 和 `DayofWeek` 两个域。它也匹配域的任意值，但实际不会。因为 `DayofMonth` 和 `DayofWeek` 会相互影响。例如想在每月的 `20` 日触发调度，不管 `20` 日到底是星期几，则只能使用如下写法： `13 13 15 20 _ ?`, 其中最后一位只能用`？`，而不能使用\_，如果使用\*表示不管星期几都会触发，实际上并不是这样。

（3）`-`：表示范围。例如在 `Minutes` 域使用 `5-20`，表示从 `5` 分到 `20` 分钟每分钟触发一次

（4）`/`：表示起始时间开始触发，然后每隔固定时间触发一次。例如在 `Minutes` 域使用 `5/20`，则意味着 `5` 分钟触发一次，而 `25，45` 等分别触发一次.

（5）`,`：表示列出枚举值。例如：在 `Minutes` 域使用 `5,20`，则意味着在 `5` 和 `20` 分每分钟触发一次。

（6）`L`：表示最后，只能出现在 `DayofWeek` 和 `DayofMonth` 域。如果在 `DayofWeek` 域使用 `5L`,意味着在最后的一个星期四触发。

（7）`W`：表示有效工作日(周一到周五) 只能出现在 `DayofMonth` 域，系统将在离指定日期的最近的有效工作日触发事件。例如：在 `DayofMonth` 使用 `5W`，如果 `5` 日是星期六，则将在最近的工作日：星期五，即 `4` 日触发。如果 `5` 日是星期天，则在 `6` 日(周一)触发；如果 `5` 日在星期一到星期五中的一天，则就在 `5` 日触发。另外一点，`W` 的最近寻找不会跨过月份 。

（8）`LW`：这两个字符可以连用，表示在某个月最后一个工作日，即最后一个星期五。

（9）`#`：用于确定每个月第几个星期几，只能出现在 `DayofMonth` 域。例如在 `4#2`，表示某月的第二个星期三。

### 25.5.2 常见 `Cron` 表达式

| 表达式                   | 表达式代表含义                                             | 格式化                      |
| ------------------------ | ---------------------------------------------------------- | --------------------------- |
| `* * * * *`              | 每分钟                                                     | `CronFormat.Standard`       |
| `*/1 * * * *`            | 每分钟                                                     | `CronFormat.Standard`       |
| `0 0/1 * * * ?`          | 每分钟                                                     | `CronFormat.IncludeSeconds` |
| `0 0 * * * ?`            | 每小时                                                     | `CronFormat.IncludeSeconds` |
| `0 0 0/1 * * ?`          | 每小时                                                     | `CronFormat.IncludeSeconds` |
| `0 23 ? * MON-FRI`       | 晚上 11:00，周一至周五                                     | `CronFormat.Standard`       |
| `* * * * * *`            | 每秒                                                       | `CronFormat.IncludeSeconds` |
| `*/45 * * * * *`         | 每 45 秒                                                   | `CronFormat.IncludeSeconds` |
| `*/5 * * * *`            | 每 5 分钟                                                  | `CronFormat.Standard`       |
| `0 0/10 * * * ?`         | 每 10 分钟                                                 | `CronFormat.IncludeSeconds` |
| `0 */5 * * * *`          | 每 5 分钟                                                  | `CronFormat.IncludeSeconds` |
| `30 11 * * 1-5`          | 周一至周五上午 11:30                                       | `CronFormat.Standard`       |
| `30 11 * * *`            | 11:30                                                      | `CronFormat.Standard`       |
| `0-10 11 * * *`          | 上午 11:00 至 11:10 之间的每一分钟                         | `CronFormat.Standard`       |
| `* * * 3 *`              | 每分钟，只在 3 月份                                        | `CronFormat.Standard`       |
| `* * * 3,6 *`            | 每分钟，只在 3 月和 6 月                                   | `CronFormat.Standard`       |
| `30 14,16 * * *`         | 下午 02:30 分和 04:30 分                                   | `CronFormat.Standard`       |
| `30 6,14,16 * * *`       | 早上 06:30，下午 02:30 和 04:30                            | `CronFormat.Standard`       |
| `46 9 * * 1`             | 早上 09:46，只在星期一                                     | `CronFormat.Standard`       |
| `23 12 15 * *`           | 下午 12:23，在本月的第 15 天                               | `CronFormat.Standard`       |
| `23 12 * JAN *`          | 下午 12:23，只在 1 月份                                    | `CronFormat.Standard`       |
| `23 12 ? JAN *`          | 下午 12:23，只在 1 月份                                    | `CronFormat.Standard`       |
| `23 12 * JAN-FEB *`      | 下午 12:23，1 月至 2 月                                    | `CronFormat.Standard`       |
| `23 12 * JAN-MAR *`      | 下午 12:23，1 月至 3 月                                    | `CronFormat.Standard`       |
| `23 12 * * SUN`          | 下午 12:23，仅在星期天                                     | `CronFormat.Standard`       |
| `*/5 15 * * MON-FRI`     | 每 5 分钟，下午 0:00 至 03:59，周一至周五                  | `CronFormat.Standard`       |
| `* * * * MON#3`          | 每分钟，在月的第三个星期一                                 | `CronFormat.Standard`       |
| `* * * * 4L`             | 每一分钟，在本月的最后一天                                 | `CronFormat.Standard`       |
| `*/5 * L JAN *`          | 每月一次每月 5 分钟，只在 1 月份                           | `CronFormat.Standard`       |
| `30 02 14 * * *`         | 下午在 02:02:30                                            | `CronFormat.IncludeSeconds` |
| `5-10 * * * * *`         | 每分钟的 5-10 秒                                           | `CronFormat.IncludeSeconds` |
| `5-10 30-35 10-12 * * *` | 10:00 至 12:00 之间的每分钟 5-10 秒，每小时 30-35 分钟     | `CronFormat.IncludeSeconds` |
| `30 */5 * * * *`         | 每分钟的 30 秒，每五分钟                                   | `CronFormat.IncludeSeconds` |
| `0 30 10-13 ? * WED,FRI` | 每小时的 30 分钟，下午 10:00 至 01:00 之间，仅在周三和周五 | `CronFormat.IncludeSeconds` |
| `10 0/5 * * * ?`         | 每分钟的 10 秒，每 05 分钟                                 | `CronFormat.IncludeSeconds` |
| `0 0 6 1/1 * ?`          | 下午 06:00                                                 | `CronFormat.IncludeSeconds` |
| `0 5 0/1 * * ?`          | 一个小时的 05 分                                           | `CronFormat.IncludeSeconds` |
| `0 0 L * *`              | 每月最后一天上午 00：00                                    | `CronFormat.Standard`       |
| `0 0 L-1 * *`            | 每月最后一天的凌晨 00：00                                  | `CronFormat.Standard`       |
| `0 0 3W * *`             | 每月第 3 个工作日上午 00：00                               | `CronFormat.Standard`       |
| `0 0 LW * *`             | 在每月的最后一个工作日，上午 00：00                        | `CronFormat.Standard`       |
| `0 0 * * 2L`             | 本月最后一个星期二上午 00：00                              | `CronFormat.Standard`       |
| `0 0 * * 6#3`            | 每月第三个星期六上午 00：00                                | `CronFormat.Standard`       |
| `0 0 ? 1 MON#1`          | 1 月第一个星期一上午 00：00                                | `CronFormat.Standard`       |

### 25.5.3 在线生成 `Cron` 表达式

[https://cron.qqe2.com/](https://cron.qqe2.com/)

### 25.5.4 `Macro` 标识符

为了方便定义 `Cron` 表达式，`Furion` 框架也提供了非常方便的占位符实现常用的时间格式：

| 占位符          | 对应表达式    | 占位符代表含义                 |
| --------------- | ------------- | ------------------------------ |
| `@every_second` | `* * * * * *` | 一秒钟跑一次                   |
| `@every_minute` | `* * * * *`   | 在分钟开始时每分钟运行一次     |
| `@hourly`       | `0 * * * *`   | 在小时开始时每小时运行一次     |
| `@daily`        | `0 0 * * *`   | 每天午夜运行一次               |
| `@midnight`     | `0 0 * * *`   | 每天午夜运行一次               |
| `@weekly`       | `0 0 * * 0`   | 周日上午午夜每周运行一次       |
| `@monthly`      | `0 0 1 * *`   | 每月在每月第一天的午夜运行一次 |
| `@yearly`       | `0 0 1 1 *`   | 每年 1 月 1 日午夜运行一次     |
| `@annually`     | `0 0 1 1 *`   | 每年 1 月 1 日午夜运行一次     |

### 25.5.5 使用 `Cron` 表达式

```cs {2,5}
// 每隔 1s 执行
SpareTime.Do("* * * * * *", (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
}, cronFormat: CronFormat.IncludeSeconds);
```

:::important 关于 CronFormat

默认情况下，`Furion` 框架未启用对 `秒` 的支持，如需开启，则设置 `cronFormat: CronFormat.IncludeSeconds` 即可。默认值是 `cronFormat: CronFormat.Standard`

:::

### 25.5.6 使用 `Macro` 占位符

```cs {2}
// 每隔 1s 执行
SpareTime.Do("@every_second", (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
});
```

### 25.5.7 配置任务信息

```cs {4}
SpareTime.Do("* * * * *", (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
}, "cronName", "每分钟执行一次");
```

### 25.5.8 手动启动执行

```cs {4,6}
SpareTime.Do("* * * * *", (timer, count) => {
    Console.WriteLine("现在时间：" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
}, "cronName", "每分钟执行一次", startNow: false);

SpareTime.Start("cronName");
```

### 26.5.9 `ISpareTimeWorker` 方式

```cs {1,8}
public class JobWorker : ISpareTimeWorker
{
    /// <summary>
    /// 每分钟执行
    /// </summary>
    /// <param name="timer"></param>
    /// <param name="count"></param>
    [SpareTime("* * * * *", "jobName", StartNow = true)]
    public void DoSomething(SpareTimer timer, long count)
    {
        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        Console.WriteLine($"一共执行了：{count} 次");
    }
}
```

## 26.6 自定义下次执行时间

有些时候我们需要进行一些业务逻辑，比如数据库查询等操作返回下一次执行时间，这个时候我们可以通过高级自定义方式。

### 26.6.1 高级自定义间隔方式

```cs {2,4-5,7}
SpareTime.Do(()=>{
    // 这里可以查询数据库或进行或进行任何业务逻辑

    if(符合逻辑){
        return 1000; // 每秒执行
    }
    else return -1; // 不符合逻辑取消任务
},
(timer,count)=>{
    Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
});
```

:::important 配置是否持续检查

默认情况下，该自定义会在返回 `小于或等于0` 时终止任务的执行。但是我们希望该任务不要终止，只要符合条件都一直执行，只需要配置 `cancelInNoneNextTime: false` 即可

:::

### 26.6.2 高级自定义 `Cron` 表达式

```cs {2,4-5,7}
SpareTime.Do(()=>{
    // 这里可以查询数据库或进行或进行任何业务逻辑

    if(符合逻辑){
        return DateTime.Now.AddMinutes(10);  // 十分钟后再执行
    }
    else return null; // 不符合逻辑取消任务
},
(timer,count) => {
    Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
});
```

:::important 配置是否持续检查

默认情况下，该自定义会在返回 `null` 时终止任务的执行。但是我们希望该任务不要终止，只要符合条件都一直执行，只需要配置 `cancelInNoneNextTime: false` 即可，如：

```cs {2,4-5,7,12}
SpareTime.Do(()=>{
    // 这里可以查询数据库或进行或进行任何业务逻辑

    if(符合逻辑){
        return SpareTime.GetCronNextOccurrence("cron 表达式");
    }
    else return null; // 不符合逻辑继续检查
},
(timer,count) => {
    Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
}, cancelInNoneNextTime: false);
```

:::

## 26.7 `ISpareTimeWorker` 说明

除了上面的 `SpareTime` 静态类方式，`Furion` 框架还提供了 `ISpareTimeWorker` 方式，使用该方式非常简单，只需要自定义一个**公开且非抽象非静态**类并实现 `ISpareTimeWorker` 即可。

在该类中定义的任务方法需满足以下规则：

- 必须是**公开且实例方法**
- 该方法必须返回 `void` 且提供 `SpareTimer` 和 `long` 两个参数
- 必须贴有 `[SpareTime]` 特性

如：

```cs {1,4-5,12-13,20-21,28-30,37-38}
public class JobWorker : ISpareTimeWorker
{
    // 每个一秒执行，且立即启动
    [SpareTime(1000, "jobName1", StartNow = true)]
    public void DoSomething1(SpareTimer timer, long count)
    {
        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        Console.WriteLine($"一共执行了：{count} 次");
    }

    // 每分钟执行，且立即启动
    [SpareTime("* * * * *", "jobName2", StartNow = true)]
    public void DoSomething2(SpareTimer timer, long count)
    {
        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        Console.WriteLine($"一共执行了：{count} 次");
    }

    // 每秒执行，且等待启动
    [SpareTime("* * * * * *", "jobName3",CronFormat = CronFormat.IncludeSeconds, StartNow = false)]
    public void DoSomething3(SpareTimer timer, long count)
    {
        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        Console.WriteLine($"一共执行了：{count} 次");
    }

    // 每秒执行一次，每分钟也执行一次
    [SpareTime(1000, "jobName4", StartNow = true)]
    [SpareTime("* * * * *", "jobName5", StartNow = true)]
    public void DoSomething4(SpareTimer timer, long count)
    {
        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        Console.WriteLine($"一共执行了：{count} 次");
    }

    // 只执行一次
    [SpareTime(1000, "jobName4", StartNow = true, DoOnce = true)]
    public void DoSomething5(SpareTimer timer, long count)
    {
        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        Console.WriteLine($"一共执行了：{count} 次");
    }
}
```

:::warning 关于依赖注入

`ISpareTimeWorker` 接口主要是用来查找定时器对象的，也就是它的实现类并未提供依赖注入功能，所以在实现类并不支持构造函数注入依赖项。

:::

### 26.7.1 `[SpareTime]` 特性

`[SpareTime]` 支持以下配置属性

- `Interval`：间隔时间, `double` 类型
- `CronExpression`：`Cron` 表达式，`string` 类型
- `WorkerName`：任务唯一标识，`string` 类型，`必填`
- `Description`：任务描述，`string` 类型
- `DoOnce`：是否只执行一次，`bool` 类型，默认 `false`
- `StartNow`：是否立即启动，默认 `false`
- `CronFormat`：`Cron` 表达式格式化方式，`CronFormat` 枚举类型，默认 `CronFormat.Standard`
- `ExecuteType`：配置任务执行方式，`SpareTimeExecuteTypes` 枚举类型，默认 `SpareTimeExecuteTypes.Parallel`

## 26.8 `SpareTime` 静态类

`SpareTime` 静态类提供了一些方法方便初始化和管理任务

### 26.8.1 初始化任务

```cs
// 开启间隔任务
SpareTime.Do(interval, [options]);

// 开启 Cron 表达式任务
SpareTime.Do(expression, [options]);

// 只执行一次任务
SpareTime.DoOnce(interval, [options]);

// 实现自定义任务
SpareTime.Do(()=>{
    return DateTime.Now.AddMinutes(10);
},[options]);
```

### 26.8.2 实现后台执行

```cs
// 实现后台执行
SpareTime.DoIt(()=>{});
```

### 26.8.3 开始一个任务

```cs
SpareTime.Start("任务标识");
```

### 26.8.4 暂停一个任务

```cs
SpareTime.Stop("任务标识");
// 还可以标记一个任务执行失败
SpareTime.Stop("任务标识", true);
```

### 26.8.5 取消一个任务

```cs
SpareTime.Cancel("任务名称");
```

### 26.8.6 销毁所有任务

```cs
SpareTime.Dispose();
```

### 26.8.7 获取所有任务

```cs
var workers = SpareTime.GetWorkers();
```

### 26.8.8 解析 `Cron` 表达式

```cs
var nextTime = SpareTime.GetCronNextOccurrence("* * * * *");
```

## 26.9 `并行`和`串行`执行方式

`Furion` 框架提供了任务两种执行方式 `并行` 和 `串行`：

- `并行`：无需等待上一次任务完成，**默认值**
- `串行`：需等待上一次任务完成

### 26.9.1 `SpareTime` 静态方式指定

```cs {3,5}
SpareTime.Do(1000, (t, i) =>
{
    Thread.Sleep(5000); // 模拟执行耗时任务
    Console.WriteLine($"{t.WorkerName} -{t.Description} - {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {i}");
}, "serialName", "模拟串行任务", executeType: SpareTimeExecuteTypes.Serial);
```

### 26.9.2 `ISpareTimeWorker` 方式

```cs {1}
[SpareTime(1000, "jobName1", StartNow = true, ExecuteType = SpareTimeExecuteTypes.Serial)]
public void DoSomething1(SpareTimer timer, long count)
{
    Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
    Console.WriteLine($"一共执行了：{count} 次");
}
```

## 26.10 任务异常处理

有些时候我们可能在执行任务过程中出现异常，`Furion` 也提供了属性判断是否有异常和异常信息，方便记录到日志中，如：

```cs {4-7,11}
SpareTime.Do(1000, (t, c) =>
{
    // 判断是否有异常
    if (t.Exception.Any())
    {
        Console.WriteLine(t.Exception.Values.LastOrDefault()?.Message);
    }
    // 执行第三次抛异常
    if (c > 2)
    {
        throw Oops.Oh("抛异常" + c);
    }
    else
    {
        Console.WriteLine($"{t.WorkerName} -{t.Description} - {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {c}");
    }
}, "exceptionJob");
```

:::warning 特别说明

如果一个任务错误次数达 `10次` 则任务将自动停止，并标记任务状态为 `Failed`。

:::

## 26.11 如何在任务中解析对象

有些时候我们需要在任务中进行数据库操作或解析服务，这时候我们只需要创建一个新的作用域即可

```cs {1,3}
var scopeFactory = App.GetService<IServiceScopeFactory>();

SpareTime.Do(1000, (timer,count) => {
    using (var scope = scopeFactory.CreateScope())
    {
        var services = scope.ServiceProvider;

        // 获取数据库上下文
        var dbContext = Db.GetDbContext(services);

        // 获取仓储
        var respository = Db.GetRepository<Person>(services);

        // 解析其他服务
        var otherService = services.GetService<XXX>();
        var otherService2 = App.GetService<XXX>(services);
    }

}, "任务标识");
```

## 26.12 在 `BackgroundService` 中使用

`BackgroundService` 是 `.NET Core 3` 之后提供的轻量级后台任务，同时可以发布到 `Windows` 服务和 `Linux` 守护进程中。

### 26.12.1 间隔执行方式

```cs {23-27}
using Furion.TaskScheduler;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace WorkerService1
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                // 间隔执行任务
                await SpareTime.DoAsync(1000, () =>
                {
                    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                }, stoppingToken);
            }
        }
    }
}
```

### 26.12.2 `Cron` 表达式执行方式，

```cs {23-27}
using Furion.TaskScheduler;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace WorkerService1
{
    public class Worker : BackgroundService
    {
        private readonly ILogger<Worker> _logger;

        public Worker(ILogger<Worker> logger)
        {
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                // 执行 Cron 表达式任务
                await SpareTime.DoAsync("*/5 * * * * *", () =>
                {
                    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                }, stoppingToken, CronFormat.IncludeSeconds);
            }
        }
    }
}
```

## 26.13 反馈与建议

:::note 与我们交流

给 Furion 提 [Issue](https://gitee.com/dotnetchina/Furion/issues/new?issue)。

:::
